/**    
 * 文件名：OpenCVUtils.java    
 *    
 * 版本信息：    
 * 日期：2014年3月7日    
 * xyj 足下 xyj 2014     
 * 版权所有    
 *    
 */
package opencvtest;

import static com.googlecode.javacv.cpp.opencv_core.IPL_DEPTH_32F;
import static com.googlecode.javacv.cpp.opencv_core.IPL_DEPTH_8U;
import static com.googlecode.javacv.cpp.opencv_core.cvConvertScale;
import static com.googlecode.javacv.cpp.opencv_core.cvCreateImage;
import static com.googlecode.javacv.cpp.opencv_core.cvGetImage;
import static com.googlecode.javacv.cpp.opencv_core.cvGetSize;
import static com.googlecode.javacv.cpp.opencv_core.cvMinMaxLoc;
import static com.googlecode.javacv.cpp.opencv_highgui.cvLoadImage;
import static com.googlecode.javacv.cpp.opencv_highgui.cvLoadImageM;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import javax.swing.JFrame;

import com.googlecode.javacpp.Pointer;
import com.googlecode.javacv.CanvasFrame;
import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvRect;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_core.IplROI;
import com.googlecode.javacv.cpp.opencv_features2d.DMatch;

/**
 * @项目名称：opencv-test
 * @类名称：OpenCVUtils
 * @类描述：
 * @创建人：zhuyi
 * @创建时间：2014年3月7日 上午10:26:01
 * @修改人：zhuyi
 * @修改时间：2014年3月7日 上午10:26:01
 * @修改备注：
 * @version
 * 
 */
public class OpenCVUtils {

    /**
     * Load an image and show in a CanvasFrame.
     * 
     * If image cannot be loaded the application will exit with code 1.
     * 
     * @param file
     *            image file
     * @param flags
     *            Flags specifying the color type of a loaded image:
     *            <ul>
     *            <li>`>0` Return a 3-channel color image</li>
     *            <li>`=0` Return a grayscale image</li>
     *            <li>`<0` Return the loaded image as is. Note that in the
     *            current implementation the alpha channel, if any, is stripped
     *            from the output image. For example, a 4-channel RGBA image is
     *            loaded as RGB if the `flags` is greater than 0.</li>
     *            </ul>
     *            Default is grayscale.
     * @return Loaded image
     */
    public static IplImage loadAndShowOrExit(File file, int flags) {
        IplImage image = null;
        try {
            image = loadOrExit(file, flags);
            show(image, file.getName());
        } catch (IOException e) {
            System.out.println("Couldn't load image: " + file.getAbsolutePath());
            System.exit(1);
        }
        return image;
    }

    /**
     * Load an image, if image cannot be loaded the application will exit with
     * code 1.
     * 
     * @param file
     *            image file
     * @param flags
     *            Flags specifying the color type of a loaded image:
     *            <ul>
     *            <li>`>0` Return a 3-channel color image</li>
     *            <li>`=0` Return a grayscale image</li>
     *            <li>`<0` Return the loaded image as is. Note that in the
     *            current implementation the alpha channel, if any, is stripped
     *            from the output image. For example, a 4-channel RGBA image is
     *            loaded as RGB if the `flags` is greater than 0.</li>
     *            </ul>
     *            Default is grayscale.
     * @throws FileNotFoundException
     *             when file does not exist
     * @throws IOException
     *             when image cannot be read
     * @return Loaded image
     */
    public static IplImage loadOrExit(File file, int flags) throws IOException {
        if (!file.exists()) {
            throw new FileNotFoundException("Image file does not exit: " + file.getAbsolutePath());
        }

        IplImage image = cvLoadImage(file.getAbsolutePath(), flags);
        if (image == null) {
            throw new IOException("Couldn't load image: " + file.getAbsolutePath());
        }

        return image;
    }

    public static IplImage toIplImage(CvMat mat) {
        IplImage image = cvCreateImage(mat.cvSize(), mat.elemSize(), 1);
        cvGetImage(mat, image);
        return image;
    }

    /**
     * Load an image and show in a CanvasFrame. If image cannot be loaded the
     * application will exit with code 1.
     * 
     * @param flags
     *            Flags specifying the color type of a loaded image:
     *            <ul>
     *            <li>`>0` Return a 3-channel color image</li>
     *            <li>`=0` Return a grayscale image</li>
     *            <li>`<0` Return the loaded image as is. Note that in the
     *            current implementation the alpha channel, if any, is stripped
     *            from the output image. For example, a 4-channel RGBA image is
     *            loaded as RGB if the `flags` is greater than 0.</li>
     *            </ul>
     *            Default is grayscale.
     * @return loaded image
     */
    public static CvMat loadMatAndShowOrExit(File file, int flags) {
        CvMat image = loadMatOrExit(file, flags);
        show(image, file.getName());
        return image;
    }

    /**
     * Load an image. If image cannot be loaded the application will exit with
     * code 1.
     * 
     * @param flags
     *            Flags specifying the color type of a loaded image:
     *            <ul>
     *            <li>`>0` Return a 3-channel color image</li>
     *            <li>`=0` Return a grayscale image</li>
     *            <li>`<0` Return the loaded image as is. Note that in the
     *            current implementation the alpha channel, if any, is stripped
     *            from the output image. For example, a 4-channel RGBA image is
     *            loaded as RGB if the `flags` is greater than 0.</li>
     *            </ul>
     *            Default is grayscale.
     * @return loaded image
     */
    public static CvMat loadMatOrExit(File file, int flags) {
        CvMat image = cvLoadImageM(file.getAbsolutePath(), flags);
        if (image == null) {
            System.out.println("Couldn't load image: " + file.getAbsolutePath());
            System.exit(1);
        }
        return image;
    }

    /**
     * Create an `IplROI`.
     * 
     * @param x
     *            top left corner of the ROI
     * @param y
     *            top left corner of the ROI
     * @param width
     *            width of the ROI
     * @param height
     *            height of the ROI
     * @return new IplROI object.
     */
    public static IplROI toIplROI(int x, int y, int width, int height) {
        return toIplROI(new Rectangle(x, y, width, height));
    }

    /**
     * Convert a rectangle to `IplROI`.
     * 
     * @param r
     *            rectangle defining location of an ROI.
     * @return new IplROI object.
     */
    public static IplROI toIplROI(Rectangle r) {
        IplROI roi = new IplROI();
        roi.xOffset(r.x);
        roi.yOffset(r.y);
        roi.width(r.width);
        roi.height(r.height);

        return roi;
    }

    public static void show(CvMat mat, String title) {
        CanvasFrame canvas = new CanvasFrame(title);
        canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        canvas.showImage(toIplImage(mat));
    }

    public static void show(Image image, String title) {
        CanvasFrame canvas = new CanvasFrame(title);
        canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        canvas.showImage(image);
    }

    /**
     * Show image in a window. Closing the window will exit the application.
     */
    public static void show(IplImage iplImage, String title) {
        CanvasFrame canvas = new CanvasFrame(title);
        canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        canvas.showImage(iplImage);
    }

    /**
     * Draw a shape on an image.
     * 
     * @param image
     *            input image
     * @param overlay
     *            shape to draw
     * @param color
     *            color to use
     * @return new image with drawn overlay
     */
    public static Image drawOnImage(IplImage image, Rectangle overlay, Color color) {

        BufferedImage bi = image.getBufferedImage();
        BufferedImage canvas = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics g = canvas.getGraphics();
        g.drawImage(bi, 0, 0, null);
        g.setColor(color);
        g.drawRect(overlay.x, overlay.y, overlay.width, overlay.height);
        g.dispose();

        return canvas;
    }

    /**
     * Convert `IplImage` to one where pixels are represented as 8 bit unsigned
     * integers (`IPL_DEPTH_8U`).
     * 
     * It creates a copy of the input image.
     * 
     * @param src
     *            input image.
     * @return copy of the input with pixels values represented as 32 floating
     *         point numbers
     */
    public static IplImage toIplImage8U(IplImage src, Boolean doScaling) {
        double[] min = new double[] { Double.MIN_VALUE };
        double[] max = new double[] { Double.MAX_VALUE };
        cvMinMaxLoc(src, min, max);
        double scale = 0;
        double offset = 0;
        if (doScaling) {
            scale = 255 / (max[0] - min[0]);
            offset = -min[0];
        } else {
            scale = 1d;
            offset = 0d;
        }

        IplImage dest = IplImage.create(cvGetSize(src), IPL_DEPTH_8U, src.nChannels());
        cvConvertScale(src, dest, scale, offset);
        return dest;
    }

    /**
     * Convert `IplImage` to one where pixels are represented as 32 floating
     * point numbers (`IPL_DEPTH_32F`).
     * 
     * It creates a copy of the input image.
     * 
     * @param src
     *            input image.
     * @return copy of the input with pixels values represented as 32 floating
     *         point numbers
     */
    public static IplImage toIplImage32F(IplImage src) {
        IplImage dest = IplImage.create(cvGetSize(src), IPL_DEPTH_32F, src.nChannels());
        cvConvertScale(src, dest, 1, 0);
        return dest;
    }

    /** Convert `CvRect` to AWT `Rectangle`. */
    public static Rectangle toRectangle(CvRect rect) {
        return new Rectangle(rect.x(), rect.y(), rect.width(), rect.height());
    }

    /**
     * Scale input image pixel values so the minimum value is 0 and maximum is
     * 1.
     * 
     * This mostly used to prepare a gray scale floating point images for
     * display.
     * 
     * @param image
     *            input image
     * @return 32 bit floating point image (depth = `IPL_DEPTH_32F`).
     */
    public static IplImage scaleTo01(IplImage image) {
        double[] max = new double[] { Double.MAX_VALUE };
        double[] min = new double[] { Double.MIN_VALUE };
        cvMinMaxLoc(image, min, max);
        double scale = 1 / (max[0] - min[0]);
        IplImage imageScaled = IplImage.create(cvGetSize(image), IPL_DEPTH_32F, image.nChannels());
        cvConvertScale(image, imageScaled, scale, 0);
        return imageScaled;
    }

    public static DMatch[] toArray(DMatch matches) {
        int oldPosition = matches.position();
        DMatch[] result = new DMatch[matches.capacity()];
        for (int i = 0; i < result.length; i++) {
            DMatch src = matches.position(i);
            DMatch dest = new DMatch();
            copy(src, dest);
            result[i] = dest;
        }
        // Reset position explicitly to avoid issues from other uses of this
        // position-based container.
        matches.position(oldPosition);

        return result;
    }

    /**
     * Copy content of a single object referred to by a pointer.
     * 
     * If `src` is an "array", only the first element is copied.
     * 
     * @param src
     *            source.
     * @param dest
     *            destination.
     */
    public static <T extends Pointer> void copy(T src, T dest) {
        dest.put(src.limit(src.position() + 1));
    }

    /**
     * Convert a Scala collection to a JavaCV "vector".
     * 
     * @param src
     *            Scala collection
     * @return JavaCV/native collection
     */
    // public static DMatch toNativeVector(DMatch[] src) {
    // DMatch[] dest = new DMatch[src.length]
    // for (i <- 0 until src.length) {
    // // Since there is no way to `put` objects into a vector DMatch,
    // // We have to reassign all values individually, and hope that API will
    // not any new ones.
    // copy(src(i), dest.position(i))
    // }
    // // Set position to 0 explicitly to avoid issues from other uses of this
    // position-based container.
    // dest.position(0)
    //
    // dest
    // }
}
